<?php
/**
 * OpenID 2.0 Authentication for PHP
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * Copyright (c) 2006-2007 Pádraic Brady <padraic.brady@yahoo.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    * The name of the author may not be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category    Authentication
 * @package     OpenID
 * @author      Pádraic Brady <padraic.brady@yahoo.com>
 * @license     http://opensource.org/licenses/bsd-license.php New BSD License
 * @version     $Id: Association.php 57 2007-07-17 20:03:27Z padraic $
 * @link        http://
 */

/** HTTP_Request */
require_once 'HTTP/Request.php';

/** Crypt_DiffieHellman */
require_once 'Crypt/DiffieHellman.php';

/** OpenID_Response_Exception */
require_once 'OpenID/Request/Exception.php';

/** OpenID_Response_Association */
require_once 'OpenID/Response/Association.php';

/**
 * OpenID Authentication 2.0 implementation in PHP5.
 *
 * @category   Zend
 * @package    OpenID
 * @copyright  Copyright (c) 2007 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class OpenID_Request_Association
{

    /**
     * Current Consumer object
     *
     * @var OpenID_Consumer
     */
    protected $_consumer = null;

    /**
     * Cached object for Diffie-Hellman Key Agreement
     *
     * @var Crypt_DiffieHellman
     */
    protected $_diffieHellman = null;

    /**
     * Constructor
     *
     * @param OpenID_Consumer $consumer
     */
    public function __construct(OpenID_Consumer $consumer)
    {
        $this->_consumer = $consumer;
    }

    /**
     * Perform an association request and return the response data
     *
     * @todo Detect stateless mode and amend the default params
     * @return OpenID_Response_Association
     */
    public function request()
    {
        /**
         * Set parameters for association request
         */
        $params = array();
        $params['openid.mode'] = 'associate';

        // If no-encryption option was used, do not utilise Diffie-Hellman Key Exchange
        if ($this->_consumer->getEncryption()) {
            $prime = OpenID::OPENID_DIFFIEHELLMAN_DEFAULT_PRIME;
            $generator = OpenID::OPENID_DIFFIEHELLMAN_DEFAULT_GENERATOR;
            $diffieHellman = new Crypt_DiffieHellman($prime, $generator);
            $this->_setDiffieHellman($diffieHellman);
            $diffieHellman->generateKeys();
            $params['openid.dh_modulus'] = OpenID::OPENID_DIFFIEHELLMAN_DEFAULT_PRIME_BASE64;
            $params['openid.dh_gen'] = OpenID::OPENID_DIFFIEHELLMAN_DEFAULT_GENERATOR_BASE64;
            $params['openid.dh_consumer_public'] = base64_encode($diffieHellman->getPublicKey(Crypt_DiffieHellman::BTWOC));
        }

        if (OpenID::getVersion() == '2.0') {
            $params['openid.ns'] = OpenID::OPENID_2_0_NAMESPACE;
            $params['openid.assoc_type'] = 'HMAC-' . OpenID::OPENID_2_0_HASH_ALGORITHM;
            $params['openid.session_type'] = 'DH-' . OpenID::OPENID_2_0_HASH_ALGORITHM;
        } else {
            $params['openid.assoc_type'] = 'HMAC-' . OpenID::OPENID_1_1_HASH_ALGORITHM;
            $params['openid.session_type'] = 'DH-' . OpenID::OPENID_1_1_HASH_ALGORITHM;
        }
        if (!$this->_consumer->getEncryption()) {
            $params['openid.session_type'] = OpenID::OPENID_NO_ENCRYPTION;
        }


        while(true) {
            $response = $this->_makeRequest($this->_consumer->getOpEndpoint(), $params);
            if (!$response->isError()) {
                return $response;
            }

            $kv = $response->getKV();

            if (isset($kv->error_code) && $kv->error_code == 'unsupported-type') {

                if ($params['openid.session_type'] == 'DH-' . OpenID::OPENID_2_0_HASH_ALGORITHM) {
                    $params['openid.session_type'] = 'DH-' . OpenID::OPENID_1_1_HASH_ALGORITHM;
                    $params['openid.assoc_type'] = 'HMAC-' . OpenID::OPENID_1_1_HASH_ALGORITHM;
                } else if ($params['openid.session_type'] == 'DH-' . OpenID::OPENID_1_1_HASH_ALGORITHM) {
                    $params['openid.session_type'] = OpenID::OPENID_NO_ENCRYPTION;
                }   
            } else {
                return $response;
            }
        }
    }

    /**
     * Return the Association constructed DiffieHellman object when we need
     * to calculate the shared key for this transaction
     *
     * @return Zend_Crypt_DiffieHellman
     */
    public function getDiffieHellman()
    {
        return $this->_diffieHellman;
    }

    /**
     * Setup Zend_Http_Client to perform the association request and compose
     * a new OpenID_Response_Association object with the response body
     *
     * @todo Code 400 responses typically contain valuable error data a user may use
     * @param string $uri
     * @param array $params
     * @return OpenID_Response_Association
     */
    protected function _makeRequest($uri, array $params)
    {
        $request = new HTTP_Request($uri);
        $request->setMethod(HTTP_REQUEST_METHOD_POST);
        $queryString = http_build_query($params, null, '&');
        $request->setBody($queryString);
        $response = $request->sendRequest();
        if (PEAR::isError($response)) {
            require_once 'OpenID/Request/Exception.php';
            throw new OpenID_Request_Exception('Invalid response to Yadis protocol received: ' . $request->getResponseCode() . ' ' . $request->getResponseBody());
        }
        $code = $request->getResponseCode();
        if ($code == 400) {
            // at some point fall back to an error response that's manageable
        }

        $associationResponse = new OpenID_Response_Association($request);
        return $associationResponse;
    }

    /**
     * Set the DiffieHellman object for this request
     *
     * @param Zend_Crypt_DiffieHellman
     * @return void
     */
    protected function _setDiffieHellman(Crypt_DiffieHellman $diffieHellman)
    {
        $this->_diffieHellman = $diffieHellman;
    }

}
